home *** CD-ROM | disk | FTP | other *** search
/ Shareware Grab Bag / Shareware Grab Bag.iso / 011 / tsrsrc20.arc / MAPMEM.PAS < prev    next >
Pascal/Delphi Source File  |  1986-06-17  |  22KB  |  678 lines

  1. {**************************************************************************
  2. *   Maps system memory blocks for MS/PCDOS 2.0 and higher.                *
  3. *   Also maps expanded memory allocation blocks                           *
  4. *   Copyright (c) 1986 Kim Kokkonen, TurboPower Software.                 *
  5. *   Released to the public domain for personal, non-commercial use only.  *
  6. ***************************************************************************
  7. *   version 1.0 1/2/86                                                    *
  8. *   version 1.1 1/10/86                                                   *
  9. *     running under DOS 2.X, where block owner names are unknown          *
  10. *   version 1.2 1/22/86                                                   *
  11. *     a bug in parsing the owner name of the block                        *
  12. *     a quirk in the way that the DOS PRINT buffer installs itself        *
  13. *     minor cosmetic changes                                              *
  14. *   version 1.3 2/6/86                                                    *
  15. *     smarter filtering for processes that deallocate their environment   *
  16. *   version 1.4 2/23/86                                                   *
  17. *     add a map of Expanded memory (EMS) as well                          *
  18. *   version 1.5 2/26/86                                                   *
  19. *     change format of last memory block                                  *
  20. *     change to more reliable scheme of finding first block               *
  21. *       (thanks to Chris Dunford for pointing out a useful                *
  22. *        undocumented DOS function).                                      *
  23. *     support environment lengths up to 32K                               *
  24. *   version 1.6 3/8/86                                                    *
  25. *     support "verbose" output mode                                       *
  26. *       display open file handles                                         *
  27. *       show command line of each block                                   *
  28. *   version 1.7 3/24/86                                                   *
  29. *     work around Turbo 3.00B bug with Delete procedure and length 255    *
  30. *     filter out command lines of programs which relocate over their      *
  31. *       command line at PSP:$80.                                          *
  32. *     fix treatment of handle counts from PSP                             *
  33. *     add display of number of memory blocks per PSP to verbose mode      *
  34. *     accept V, -V, or /V for the verbose switch                          *
  35. *   version 1.8 4/20/86                                                   *
  36. *     change verbose mode to show each block individually                 *
  37. *   version 1.9 5/22/86                                                   *
  38. *     synchronize with RELEASE                                            *
  39. *   version 2.0 6/17/86                                                   *
  40. *     synchronize with RELEASE                                            *
  41. ***************************************************************************
  42. *   telephone: 408-378-3672, CompuServe: 72457,2131.                      *
  43. *   requires Turbo version 3 to compile.                                  *
  44. *   Compile with mAx dynamic memory = FFFF.                               *
  45. ***************************************************************************}
  46.  
  47. {$P128}
  48.  
  49. program MapMem;
  50.   {-look at the system memory map using DOS memory control blocks}
  51. const
  52.   Version = '2.0';
  53.   MaxBlocks = 100;            {max number of DOS memory blocks checked}
  54.   MaxVector = $FF;            {highest interrupt vector checked for trapping}
  55. type
  56.   Block =
  57.   record                      {store info about each memory block as it is found}
  58.     idbyte : Byte;
  59.     mcb : Integer;
  60.     psp : Integer;
  61.     len : Integer;
  62.     psplen : Integer;
  63.     env : Integer;
  64.     cnt : Integer;
  65.   end;
  66.   BlockType = 0..MaxBlocks;
  67.   BlockArray = array[BlockType] of Block;
  68.   registers =
  69.   record
  70.     case Integer of
  71.       1 : (ax, bx, cx, dx, bp, si, di, ds, es, flags : Integer);
  72.       2 : (al, ah, bl, bh, cl, ch, dl, dh : Byte);
  73.   end;
  74.   Pathname = string[64];
  75.  
  76. var
  77.   Blocks : BlockArray;
  78.   BlockNum : BlockType;
  79.   verbose : Boolean;
  80.   param : Pathname;
  81.  
  82.   function StUpcase(s : Pathname) : Pathname;
  83.     {-return the upper case of a string}
  84.   var
  85.     i : Byte;
  86.   begin
  87.     for i := 1 to Length(s) do s[i] := UpCase(s[i]);
  88.     StUpcase := s;
  89.   end {stupcase} ;
  90.  
  91.   procedure FindTheBlocks;
  92.     {-scan memory for the allocated memory blocks}
  93.   const
  94.     MidBlockID = $4D;         {byte DOS uses to identify part of MCB chain}
  95.     EndBlockID = $5A;         {byte DOS uses to identify last block of MCB chain}
  96.   var
  97.     mcbSeg : Integer;         {segment address of current MCB}
  98.     nextSeg : Integer;        {computed segment address for the next MCB}
  99.     gotFirst : Boolean;       {true after first MCB is found}
  100.     gotLast : Boolean;        {true after last MCB is found}
  101.     idbyte : Byte;            {byte that DOS uses to identify an MCB}
  102.  
  103.     function GetStartMCB : Integer;
  104.       {-return the first MCB segment}
  105.     var
  106.       reg : registers;
  107.     begin
  108.       reg.ah := $52;
  109.       MsDos(reg);
  110.       GetStartMCB := MemW[reg.es:(reg.bx-2)];
  111.     end {getstartmcb} ;
  112.  
  113.     procedure StoreTheBlock(var mcbSeg, nextSeg : Integer;
  114.                             var gotFirst, gotLast : Boolean);
  115.       {-store information regarding the memory block}
  116.     var
  117.       nextID : Byte;
  118.       pspAdd : Integer;       {segment address of the current PSP}
  119.       mcbLen : Integer;       {size of the current memory block in paragraphs}
  120.  
  121.     begin
  122.  
  123.       mcbLen := MemW[mcbSeg:3]; {size of the MCB in paragraphs}
  124.       nextSeg := Succ(mcbSeg+mcbLen); {where the next MCB should be}
  125.       pspAdd := MemW[mcbSeg:1]; {address of program segment prefix for MCB}
  126.       nextID := Mem[nextSeg:0];
  127.  
  128.       if gotLast or (nextID = EndBlockID) or (nextID = MidBlockID) then begin
  129.         BlockNum := Succ(BlockNum);
  130.         gotFirst := True;
  131.         with Blocks[BlockNum] do begin
  132.           idbyte := Mem[mcbSeg:0];
  133.           mcb := mcbSeg;
  134.           psp := pspAdd;
  135.           env := MemW[pspAdd:$2C];
  136.           len := mcbLen;
  137.           psplen := 0;
  138.           cnt := 1;
  139.         end;
  140.       end;
  141.  
  142.     end {storetheblock} ;
  143.  
  144.   begin
  145.  
  146.     {initialize}
  147.     mcbSeg := GetStartMCB;
  148.     gotFirst := False;
  149.     gotLast := False;
  150.     BlockNum := 0;
  151.  
  152.     {scan all memory until the last block is found}
  153.     repeat
  154.       idbyte := Mem[mcbSeg:0];
  155.       if idbyte = MidBlockID then begin
  156.         StoreTheBlock(mcbSeg, nextSeg, gotFirst, gotLast);
  157.         if gotFirst then mcbSeg := nextSeg else mcbSeg := Succ(mcbSeg);
  158.       end else if gotFirst and (idbyte = EndBlockID) then begin
  159.         gotLast := True;
  160.         StoreTheBlock(mcbSeg, nextSeg, gotFirst, gotLast);
  161.       end else begin
  162.         {start block was invalid}
  163.         WriteLn('corrupted allocation chain or program error');
  164.         Halt(1);
  165.       end;
  166.     until gotLast;
  167.  
  168.   end {findtheblocks} ;
  169.  
  170.   function Cardinal(i : Integer) : Real;
  171.     {-return an unsigned integer 0..65535}
  172.   begin
  173.     Cardinal := 256.0*Hi(i)+Lo(i);
  174.   end {cardinal} ;
  175.  
  176.   procedure StripNonAscii(var t : Pathname);
  177.     {-return an empty string if t contains any non-printable characters}
  178.   var
  179.     ipos : Byte;
  180.     goodname : Boolean;
  181.   begin
  182.     goodname := True;
  183.     for ipos := 1 to Length(t) do
  184.       if (t[ipos] <> #0) and ((t[ipos] < ' ') or (t[ipos] > '}')) then
  185.         goodname := False;
  186.     if not(goodname) then t := '';
  187.   end {stripnonascii} ;
  188.  
  189.   procedure ShowTheBlocks;
  190.     {-analyze and display the blocks found}
  191.   type
  192.     HexString = string[4];
  193.     Address = record
  194.                 offset, segment : Integer;
  195.               end;
  196.     VectorType = 0..MaxVector;
  197.   var
  198.     st, cline : Pathname;
  199.     b : BlockType;
  200.     StLen, DOSv : Byte;
  201.     CommandPSP : Integer;
  202.     Vectors : array[VectorType] of Address absolute 0 : 0;
  203.     Vtable : array[VectorType] of Real;
  204.     SumNum : BlockType;
  205.     Sum : BlockArray;
  206.  
  207.     function Hex(i : Integer) : HexString;
  208.       {-return hex representation of integer}
  209.     const
  210.       hc : array[0..15] of Char = '0123456789ABCDEF';
  211.     var
  212.       l, h : Byte;
  213.     begin
  214.       l := Lo(i); h := Hi(i);
  215.       Hex := hc[h shr 4]+hc[h and $F]+hc[l shr 4]+hc[l and $F];
  216.     end {hex} ;
  217.  
  218.     function DOSversion : Byte;
  219.       {-return the major version number of DOS}
  220.     var
  221.       reg : registers;
  222.     begin
  223.       reg.ah := $30;
  224.       MsDos(reg);
  225.       DOSversion := reg.al;
  226.     end {dosversion} ;
  227.  
  228.     function Owner(startadd : Integer) : Pathname;
  229.       {-return the name of the owner program of an MCB}
  230.     type
  231.       chararray = array[0..32767] of Char;
  232.     var
  233.       e : ^chararray;
  234.       i : Integer;
  235.       t : Pathname;
  236.  
  237.       function LongPos(m : Pathname; var s : chararray) : Integer;
  238.         {-return the position number of m in s, or 0 if not found}
  239.       var
  240.         mc : Char;
  241.         ss : Pathname;
  242.         i, maxindex : Integer;
  243.         found : Boolean;
  244.       begin
  245.         i := 0;
  246.         maxindex := SizeOf(s)-Length(m);
  247.         ss[0] := m[0];
  248.         if Length(m) > 0 then begin
  249.           mc := m[1];
  250.           repeat
  251.             while (s[i] <> mc) and (i <= maxindex) do
  252.               i := Succ(i);
  253.             if s[i] = mc then begin
  254.               Move(s[i], ss[1], Length(m));
  255.               found := (ss = m);
  256.               if not(found) then i := Succ(i);
  257.             end;
  258.           until found or (i > maxindex);
  259.           if not(found) then i := 0;
  260.         end;
  261.         LongPos := i;
  262.       end {longpos} ;
  263.  
  264.       procedure StripPathname(var pname : Pathname);
  265.         {-remove leading drive or path name from the input}
  266.       var
  267.         spos, cpos, rpos : Byte;
  268.       begin
  269.         spos := Pos('\', pname);
  270.         cpos := Pos(':', pname);
  271.         if spos+cpos = 0 then Exit;
  272.         if spos <> 0 then begin
  273.           {find the last slash in the pathname}
  274.           rpos := Length(pname);
  275.           while (rpos > 0) and (pname[rpos] <> '\') do rpos := Pred(rpos);
  276.         end else
  277.           rpos := cpos;
  278.         Delete(pname, 1, rpos);
  279.       end {strippathname} ;
  280.  
  281.       procedure StripExtension(var pname : Pathname);
  282.         {-remove the file extension}
  283.       var
  284.         dotpos : Byte;
  285.       begin
  286.         dotpos := Pos('.', pname);
  287.         if dotpos <> 0 then
  288.           Delete(pname, dotpos, 64); {<255 needed for Turbo version 3.00B bug}
  289.       end {stripextension} ;
  290.  
  291.     begin
  292.       {point to the environment string}
  293.       e := Ptr(startadd, 0);
  294.  
  295.       {find end of the standard environment}
  296.       i := LongPos(#0#0, e^);
  297.       if i = 0 then begin
  298.         {something's wrong, exit gracefully}
  299.         Owner := '';
  300.         Exit;
  301.       end;
  302.  
  303.       {end of environment found, get the program name that follows it}
  304.       t := '';
  305.       i := i+4;               {skip over #0#0#args}
  306.       repeat
  307.         t := t+e^[i];
  308.         i := Succ(i);
  309.       until (Length(t) > 64) or (e^[i] = #0);
  310.  
  311.       StripNonAscii(t);
  312.       if t = '' then
  313.         Owner := 'N/A'
  314.       else begin
  315.         StripPathname(t);
  316.         StripExtension(t);
  317.         if t = '' then t := 'N/A';
  318.         Owner := StUpcase(t);
  319.       end;
  320.  
  321.     end {owner} ;
  322.  
  323.     procedure InitVectorTable;
  324.       {-build real equivalent of vector addresses}
  325.     var
  326.       v : VectorType;
  327.  
  328.       function RealAdd(a : Address) : Real;
  329.         {-return the real equivalent of an address (pointer)}
  330.       begin
  331.         with a do
  332.           RealAdd := 16.0*Cardinal(segment)+Cardinal(offset);
  333.       end {realadd} ;
  334.  
  335.     begin
  336.       for v := 0 to MaxVector do
  337.         Vtable[v] := RealAdd(Vectors[v]);
  338.     end {initvectortable} ;
  339.  
  340.     procedure WriteHooks(start, stop : Integer);
  341.       {-show the trapped interrupt vectors}
  342.     var
  343.       v : VectorType;
  344.       sadd, eadd : Real;
  345.     begin
  346.       if start = stop then Exit;
  347.       sadd := 16.0*Cardinal(start);
  348.       eadd := 16.0*Cardinal(stop);
  349.       for v := 0 to MaxVector do begin
  350.         if (Vtable[v] >= sadd) and (Vtable[v] <= eadd) then
  351.           Write(Copy(Hex(v), 3, 2), ' ');
  352.       end;
  353.     end {writehooks} ;
  354.  
  355.     procedure SortByPSP(var Blocks : BlockArray; BlockNum : BlockType);
  356.       {-sort in order of ascending PSP}
  357.     var
  358.       i, j : BlockType;
  359.       temp : Block;
  360.     begin
  361.       for i := 1 to Pred(BlockNum) do
  362.         for j := BlockNum downto Succ(i) do
  363.           if Cardinal(Blocks[j].psp) < Cardinal(Blocks[Pred(j)].psp) then begin
  364.             temp := Blocks[j];
  365.             Blocks[j] := Blocks[Pred(j)];
  366.             Blocks[Pred(j)] := temp;
  367.           end;
  368.     end {SortByPSP} ;
  369.  
  370.     procedure SumTheBlocks(var Blocks : BlockArray;
  371.                            BlockNum : BlockType;
  372.                            var Sum : BlockArray;
  373.                            var SumNum : BlockType);
  374.       {-combine the blocks with equivalent PSPs}
  375.     var
  376.       prevPSP : Integer;
  377.       b : BlockType;
  378.     begin
  379.       SumNum := 0;
  380.       prevPSP := 0;
  381.       for b := 1 to BlockNum do begin
  382.         if Blocks[b].psp <> prevPSP then begin
  383.           SumNum := Succ(SumNum);
  384.           Sum[SumNum] := Blocks[b];
  385.           prevPSP := Blocks[b].psp;
  386.           if prevPSP = CSeg then
  387.             {don't include the environment as part of free block's length}
  388.             Sum[SumNum].len := 0;
  389.         end else
  390.           with Sum[SumNum] do begin
  391.             cnt := Succ(cnt);
  392.             len := len+Blocks[b].len;
  393.           end;
  394.         {get length of the block which owns the executable program}
  395.         {for checking vector trapping next}
  396.         if Succ(Blocks[b].mcb) = Blocks[b].psp then
  397.           Sum[SumNum].psplen := Blocks[b].len;
  398.       end;
  399.     end {sumblocks} ;
  400.  
  401.     procedure TransferTheBlocks(var Blocks : BlockArray;
  402.                                 BlockNum : BlockType;
  403.                                 var Sum : BlockArray;
  404.                                 var SumNum : BlockType);
  405.       {-fill in the Sum array with a little initialization}
  406.     var
  407.       b : BlockType;
  408.     begin
  409.       for b := 1 to BlockNum do begin
  410.         Sum[b] := Blocks[b];
  411.         with Sum[b] do begin
  412.           cnt := 1;
  413.           if (Succ(mcb) = psp) and (psp <> 0) then
  414.             psplen := len
  415.           else
  416.             psplen := 0;
  417.         end;
  418.       end;
  419.       SumNum := BlockNum;
  420.     end {transfertheblocks} ;
  421.  
  422.     function OpenHandles(psp : Integer) : Integer;
  423.       {-return the number of open handles owned by a process}
  424.     var
  425.       h, o : Integer;
  426.       b : Byte;
  427.     begin
  428.       h := 0;
  429.       if (psp <> 8) and (cline <> 'N/A') then
  430.         for o := 0 to 19 do begin
  431.           b := Mem[psp:$18+o];
  432.           if not(b in [$FF, 0..2]) then
  433.             h := Succ(h);
  434.         end;
  435.       OpenHandles := h;
  436.     end {openhandles} ;
  437.  
  438.     function CommandLine(psp : Integer) : Pathname;
  439.       {-return the command line of the PSP}
  440.     var
  441.       t, s : Pathname;
  442.       i : Byte;
  443.     begin
  444.       if (psp <> 8) then begin
  445.         Move(Mem[psp:$80], t, 65);
  446.         if t[0] > #64 then t[0] := #64;
  447.         s := t;
  448.         StripNonAscii(t);
  449.         if s <> t then
  450.           {command line has been written over}
  451.           t := 'N/A'
  452.         else
  453.           {strip leading blanks}
  454.           while (Length(t) > 0) and (t[1] = #32) do Delete(t, 1, 1);
  455.       end else
  456.         {psp=8 is a special block owned by DOS containing the CONFIG.SYS drivers}
  457.         t := '';
  458.       CommandLine := t;
  459.     end {commandline} ;
  460.  
  461.     function PrevBlock(b : BlockType; psp : Integer) : BlockType;
  462.       {-return highest block with number less than b having a PSP matching psp}
  463.       {-return 0 if none}
  464.     var
  465.       t : BlockType;
  466.       found : Boolean;
  467.     begin
  468.       found := False;
  469.       t := Pred(b);
  470.       while (t > 0) and not(found) do begin
  471.         found := (Sum[t].psp = psp);
  472.         if not(found) then t := Pred(t);
  473.       end;
  474.       PrevBlock := t;
  475.     end {prevblock} ;
  476.  
  477.   begin
  478.     WriteLn;
  479.     Write('Allocated Memory Map - by TurboPower Software - Version ', Version);
  480.  
  481.     if verbose then begin
  482.       WriteLn('  (verbose)');
  483.       WriteLn;
  484.       WriteLn(' PSP  MCB files bytes owner    command line  hooked vectors');
  485.       WriteLn('---- ---- ----- ----- -------- ------------- -----------------------------');
  486.     end else begin
  487.       WriteLn;
  488.       WriteLn;
  489.       WriteLn(' PSP  blks bytes owner    command line        hooked vectors');
  490.       WriteLn('----- ---- ----- -------- ------------------- ------------------------------');
  491.     end;
  492.  
  493.     DOSv := DOSversion;
  494.     CommandPSP := Blocks[2].psp;
  495.     InitVectorTable;
  496.     if verbose then
  497.       TransferTheBlocks(Blocks, BlockNum, Sum, SumNum)
  498.     else begin
  499.       SortByPSP(Blocks, BlockNum);
  500.       SumTheBlocks(Blocks, BlockNum, Sum, SumNum);
  501.     end;
  502.  
  503.     for b := 1 to SumNum do with Sum[b] do begin
  504.  
  505.       {get the command line which invoked the program}
  506.       if b = SumNum then
  507.         cline := ''
  508.       else
  509.         cline := CommandLine(psp);
  510.  
  511.       {write out numerical information}
  512.       Write(Hex(psp), ' ');   {PSP address}
  513.       if verbose then begin
  514.         Write(Hex(mcb), '  ', {MCB address}
  515.         OpenHandles(psp):2, '  '); {number of open file handles}
  516.       end else
  517.         Write(cnt:3, '  ');   {number of blocks}
  518.  
  519.       Write(16.0*Cardinal(len):6:0, ' '); {size of block in bytes}
  520.  
  521.       {get the program owning this block by scanning the environment}
  522.       if psp = CSeg then
  523.         st := 'free'
  524.       else if psp = CommandPSP then
  525.         st := 'command'
  526.       else if psp = Sum[1].psp then
  527.         st := 'config'
  528.       else if (DOSv >= 3) then begin
  529.         if verbose then begin
  530.           if Succ(mcb) = env then
  531.             {this is the environment block}
  532.             st := Owner(env)
  533.           else if PrevBlock(b, psp) <> 0 then
  534.             {this is the block that goes with the environment}
  535.             st := Owner(Sum[PrevBlock(b, psp)].env)
  536.           else
  537.             st := 'N/A';
  538.         end else if cnt > 1 then
  539.           st := Owner(env)
  540.         else
  541.           st := 'N/A';
  542.       end else
  543.         st := 'N/A';
  544.       while Length(st) < 9 do st := st+' ';
  545.       Write(st);
  546.  
  547.       {write the command line that invoked the program}
  548.       if verbose then
  549.         StLen := 13
  550.       else
  551.         StLen := 19;
  552.       if Length(cline) > StLen-3 then
  553.         cline := Copy(cline, 1, StLen-3)+'...'
  554.       else
  555.         while Length(cline) < StLen do cline := cline+' ';
  556.       Write(cline, ' ');
  557.  
  558.       {write the trapped interrupt vectors}
  559.       if verbose or (b <> SumNum) then
  560.         WriteHooks(psp, psp+psplen);
  561.  
  562.       WriteLn;
  563.     end;
  564.  
  565.   end {showtheblocks} ;
  566.  
  567.   procedure ShowTheEMSblocks;
  568.     {-map out expanded memory, if present}
  569.   const
  570.     EMSinterrupt = $67;       {the vector used by the expanded memory manager}
  571.     MaxHandles = 255;
  572.  
  573.   type
  574.     HandlePageRecord =
  575.     record
  576.       handle : Integer;
  577.       numpages : Integer;
  578.     end;
  579.  
  580.     PageArray = array[0..MaxHandles] of HandlePageRecord;
  581.     PageArrayPtr = ^PageArray;
  582.     Pathname = string[64];
  583.  
  584.   var
  585.     EMSregs : registers;
  586.     EMShandles : Integer;
  587.     Map : PageArrayPtr;
  588.     TotalPages : Integer;
  589.  
  590.     function EMSpresent : Boolean;
  591.       {-return true if EMS memory manager is present}
  592.     var
  593.       f : file;
  594.       present : Boolean;
  595.     begin
  596.       {"file handle" defined by the expanded memory manager at installation}
  597.       Assign(f, 'EMMXXXX0');
  598.       {$I-} Reset(f) {$I+} ;
  599.       present := (IOResult = 0);
  600.       if present then
  601.         Close(f);
  602.       EMSpresent := present;
  603.     end {EMSpresent} ;
  604.  
  605.     function EMSpagesAvailable(var TotalPages : Integer) : Integer;
  606.       {-return the number of 16K expanded memory pages available and unallocated}
  607.     begin
  608.       EMSregs.ah := $42;
  609.       Intr(EMSinterrupt, EMSregs);
  610.       if EMSregs.ah <> 0 then begin
  611.         WriteLn('EMS device not responding');
  612.         EMSpagesAvailable := 0;
  613.         Exit;
  614.       end;
  615.       EMSpagesAvailable := EMSregs.bx;
  616.       TotalPages := EMSregs.dx;
  617.     end {EMSpagesAvailable} ;
  618.  
  619.     function EMShandlesActive : Integer;
  620.       {-return the number of active EMS handles}
  621.     begin
  622.       EMSregs.ah := $4B;
  623.       Intr(EMSinterrupt, EMSregs);
  624.       if EMSregs.ah <> 0 then begin
  625.         WriteLn('EMS device not responding');
  626.         EMShandlesActive := 0;
  627.         Exit;
  628.       end;
  629.       EMShandlesActive := EMSregs.bx;
  630.     end {EMShandlesActive} ;
  631.  
  632.     procedure EMSpageMap(var PageMap : PageArray);
  633.       {-return an array of the allocated memory blocks}
  634.     begin
  635.       EMSregs.ah := $4D;
  636.       EMSregs.es := Seg(PageMap);
  637.       EMSregs.di := Ofs(PageMap);
  638.       EMSregs.bx := 0;
  639.       Intr(EMSinterrupt, EMSregs);
  640.       if EMSregs.ah <> 0 then
  641.         WriteLn('EMS device not responding');
  642.     end {EMSpageMap} ;
  643.  
  644.     procedure WriteEMSmap(PageMap : PageArray; handles : Integer);
  645.       {-write out the EMS page map}
  646.     var
  647.       h : Integer;
  648.     begin
  649.       WriteLn('block   bytes   (Expanded Memory)');
  650.       WriteLn('-----   ------');
  651.       for h := 0 to Pred(handles) do
  652.         WriteLn(h:5, '  ', (16384.0*Cardinal(PageMap[h].numpages)):7:0);
  653.     end {writeEMSmap} ;
  654.  
  655.   begin
  656.     if not(EMSpresent) then Exit;
  657.     EMShandles := EMShandlesActive;
  658.     WriteLn;
  659.     GetMem(Map, 4*EMShandles);
  660.     EMSpageMap(Map^);
  661.     WriteEMSmap(Map^, EMShandles);
  662.     WriteLn(' free  ', (16384.0*Cardinal(EMSpagesAvailable(TotalPages))):7:0);
  663.     WriteLn('total  ', (16384.0*Cardinal(TotalPages)):7:0);
  664.   end {showtheemsblocks} ;
  665.  
  666.  
  667. begin
  668.   verbose := False;
  669.   if ParamCount > 0 then begin
  670.     param := StUpcase(ParamStr(1));
  671.     if (param = 'V') or (param = '-V') or (param = '/V') then
  672.       verbose := True;
  673.   end;
  674.   FindTheBlocks;
  675.   ShowTheBlocks;
  676.   ShowTheEMSblocks;
  677. end.
  678.